import os
import struct
import sys
import traceback

AbsolutePath = os.path.abspath(__file__)
SuperiorCatalogue = os.path.dirname(AbsolutePath)
BaseDir = os.path.dirname(SuperiorCatalogue)
sys.path.insert(0, SuperiorCatalogue)

import asyncio
import re
import time
import binascii
from typing import Optional, List
from multiprocessing import Process, Queue, Event
from multiprocessing import sharedctypes as mps
from ctypes import *
from TestToolApi import Debug

import serial


class MainBoardData(Structure):
    class CpuInfo(Structure):
        _fields_ = [
            ('usage', c_float),
            ('temp', c_float)
        ]

    class MemInfo(Structure):
        _fields_ = [
            ('total', c_float),
            ('used', c_float),
            ('free', c_float),
            ('per', c_float),
        ]

    class DistInfo(Structure):
        _fields_ = [
            ('total', c_float),
            ('used', c_float),
            ('free', c_float),
            ('per', c_float),
        ]

    class NetInfo(Structure):
        class NetPackets(Structure):
            _fields_ = [
                ('bytes', c_int32),
                ('packets', c_int32),
                ('errs', c_int32),
                ('drop', c_int32)
            ]

        _fields_ = [
            ('receive', NetPackets),
            ('transmit', NetPackets),
            ('down', c_float),
            ('up', c_float)
        ]

    _fields_ = [
        ('cpu', CpuInfo),
        ('mem', MemInfo),
        ('dist', DistInfo),
        ('net', NetInfo)
    ]


class GpsData(Structure):
    class NetStatusData(Structure):
        _fields_ = [
            ('status', c_bool),
            ('signal', c_uint8)
        ]

    class NetGpsData(Structure):
        _fields_ = [
            ('time', c_wchar_p),
            ('locStat', c_bool),
            ('dim', c_float),
            ('nsDim', c_float),
            ('lit', c_float),
            ('ewLit', c_float),
            ('gdRate', c_float),
            ('gdDirect', c_wchar_p),
            ('date', c_wchar_p),
        ]

    _fields_ = [
        ('net', NetStatusData),
        ('gps', NetGpsData)
    ]


class SerialData(Structure):
    _fields_ = [
        ('status', c_uint8),
        ('speed_stm32', c_float),
        ('yaw', c_float),
        ('speed_qt', c_float),
        ('bat', c_float)
    ]


class MainBoard(Process):

    def __init__(self, daemon=True, logging=True):
        super(MainBoard, self).__init__(daemon=daemon)
        if logging:
            self.__mainDebugQueue = Queue(5)
        else:
            self.__mainDebugQueue = None
        self.__mainDebug = Debug(self.__mainDebugQueue, time_switch=True, debug_name="下位机系统-数据模块-主板数据进程")
        self.__CloseStatus = Event()
        self.__PauseStatus = Event()
        self.MAIN_BOARD_DATA = mps.Value(MainBoardData)

    @staticmethod
    async def __cmdRun(command: str) -> str:
        proc = await asyncio.subprocess.create_subprocess_shell(
            command,
            stdout=asyncio.subprocess.PIPE,
            stderr=asyncio.subprocess.DEVNULL,
            shell=True,
        )
        std, _, = await proc.communicate()
        return std.decode()

    async def __getCpuUsage(self) -> float:
        result = await self.__cmdRun("top -n1 | awk '/Cpu\(s\):/ {print $2}'")
        result.strip()
        result = float(result)
        return result

    async def __getCpuTemp(self) -> float:
        result = await self.__cmdRun("vcgencmd measure_temp")
        result = result.replace("temp=", "").replace("'C\n", "")
        result = float(result)
        return result

    async def __getMemInfo(self) -> dict:
        result = await self.__cmdRun("free")
        line = result.split('\n')[1]
        RAM_TOTAL, RAM_USED, RAM_FREE = line.split()[1:4]
        RAM_TOTAL = round(int(RAM_TOTAL) / 1000, 2)
        RAM_USED = round(int(RAM_USED) / 1000, 2)
        RAM_FREE = round(int(RAM_FREE) / 1000, 2)
        RAM_USAGE = round(RAM_USED / RAM_TOTAL * 100, 2)
        return {'memPer': RAM_USAGE, 'memUsed': RAM_USED, 'memFree': RAM_FREE, 'memTotal': RAM_TOTAL}

    async def __getDiskInfo(self) -> dict:
        result = await self.__cmdRun("df -h /")
        line = result.split("\n")[1]
        DISK_TOTAL, DISK_USED, DISK_AVALIBLE, DISK_PERC = line.split()[1: 5]
        DISK_TOTAL = round(float(DISK_TOTAL.replace('G', '')), 2)
        DISK_USED = round(float(DISK_USED.replace('G', '')), 2)
        DISK_AVALIBLE = round(float(DISK_AVALIBLE.replace('G', '')), 2)
        DISK_PERC = round(float(DISK_PERC.replace('%', '')), 2)
        return {'distTotal': DISK_TOTAL, 'distUsed': DISK_USED, 'distFree': DISK_AVALIBLE, 'distPer': DISK_PERC}

    async def __getNetInfo(self, name: str) -> Optional[dict]:
        result = await self.__cmdRun("cat /proc/net/dev")
        result = re.split(r'\n', result)
        for i in range(2, len(result) - 1):
            result_NL = result[i].split(':')
            propertyList = re.findall(r'(\d+)', result_NL[1])
            resultList = propertyList
            devName = result_NL[0].strip()
            if devName == name:
                recv_bytes = int(resultList[0])
                recv_packets = int(resultList[1])
                recv_errs = int(resultList[2])
                recv_drop = int(resultList[3])
                trans_bytes = int(resultList[8])
                trans_packets = int(resultList[9])
                trans_errs = int(resultList[10])
                trans_drop = int(resultList[11])
                contentDict = {
                    "devName": devName,
                    "receive": {
                        "bytes": int(recv_bytes / 1024),
                        "packets": recv_packets,
                        "errs": recv_errs,
                        "drop": recv_drop,
                    },
                    "transmit": {
                        "bytes": int(trans_bytes / 1024),
                        "packets": trans_packets,
                        "errs": trans_errs,
                        "drop": trans_drop,
                    },
                }
                return contentDict
            else:
                continue
        return None

    def run(self) -> None:
        self.__mainDebug.info("进程启动中...")
        while not self.__CloseStatus.is_set():
            try:
                self.__mainDebug.info("等待开启主板数据采集服务器")
                while self.__PauseStatus.is_set():
                    if self.__CloseStatus.is_set():
                        break
                    time.sleep(0.1)
                if self.__CloseStatus.is_set():
                    break
                else:
                    self.__mainDebug.info("正在循环更新主板数据...")

                    async def main():
                        lastReceiveKBytes = 0
                        lastTransmitKBytes = 0
                        lastTime = time.time()
                        while not self.__PauseStatus.is_set():
                            try:
                                taskList = [
                                    self.__getCpuUsage(), self.__getCpuTemp(), self.__getMemInfo(),
                                    self.__getDiskInfo(), self.__getNetInfo('ppp0')
                                ]
                                resultList = await asyncio.gather(*taskList)
                                self.MAIN_BOARD_DATA.cpu.usage = resultList[0]
                                self.MAIN_BOARD_DATA.cpu.temp = resultList[1]

                                self.MAIN_BOARD_DATA.mem.total = resultList[2]['memTotal']
                                self.MAIN_BOARD_DATA.mem.used = resultList[2]['memUsed']
                                self.MAIN_BOARD_DATA.mem.free = resultList[2]['memFree']
                                self.MAIN_BOARD_DATA.mem.per = resultList[2]['memPer']

                                self.MAIN_BOARD_DATA.dist.total = resultList[3]['distTotal']
                                self.MAIN_BOARD_DATA.dist.used = resultList[3]['distUsed']
                                self.MAIN_BOARD_DATA.dist.free = resultList[3]['distFree']
                                self.MAIN_BOARD_DATA.dist.per = resultList[3]['distPer']

                                self.MAIN_BOARD_DATA.net.receive.bytes = resultList[4]['receive']['bytes']
                                self.MAIN_BOARD_DATA.net.receive.packets = resultList[4]['receive']['packets']
                                self.MAIN_BOARD_DATA.net.receive.errs = resultList[4]['receive']['errs']
                                self.MAIN_BOARD_DATA.net.receive.drop = resultList[4]['receive']['drop']
                                self.MAIN_BOARD_DATA.net.transmit.bytes = resultList[4]['transmit']['bytes']
                                self.MAIN_BOARD_DATA.net.transmit.packets = resultList[4]['transmit']['packets']
                                self.MAIN_BOARD_DATA.net.transmit.errs = resultList[4]['transmit']['errs']
                                self.MAIN_BOARD_DATA.net.transmit.drop = resultList[4]['transmit']['drop']

                                if time.time() - lastTime >= 1:
                                    self.MAIN_BOARD_DATA.net.up = self.MAIN_BOARD_DATA.net.transmit.bytes - lastTransmitKBytes
                                    self.MAIN_BOARD_DATA.net.down = self.MAIN_BOARD_DATA.net.receive.bytes - lastReceiveKBytes
                                    lastReceiveKBytes = self.MAIN_BOARD_DATA.net.receive.bytes
                                    lastTransmitKBytes = self.MAIN_BOARD_DATA.net.transmit.bytes
                                    lastTime = time.time()
                                    # print(self.MAIN_BOARD_DATA.net.up)
                                    # print(self.MAIN_BOARD_DATA.net.down)
                                # self.MAIN_BOARD_DATA.cpu.usage = await self.__getCpuUsage()
                                # self.MAIN_BOARD_DATA.cpu.temp = await self.__getCpuTemp()
                                # self.MAIN_BOARD_DATA.mem.total = await self.__
                            except Exception as e:
                                self.__mainDebug.warn("数据采集发生错误, 退出!")
                                break
                        if self.__PauseStatus.is_set() and not self.__CloseStatus.is_set():
                            self.__mainDebug.warn("出现全局错误, 退出该任务!")
                        elif self.__PauseStatus.is_set() and self.__CloseStatus.is_set():
                            self.__mainDebug.debug("上层应用请求结束采集")
                        self.__PauseStatus.set()
                        self.__mainDebug.warn("任务被中止!")

                    asyncio.run(main())
                    self.__mainDebug.info("全任务结束, 重新进入等待")
            except Exception as e:
                self.__mainDebug.error(
                    f"\n{traceback.format_exc()}\n----------Error----------"
                )
                self.__mainDebug.error("发生异常情况, 退出该进程")
                break
        if self.__CloseStatus.is_set():
            self.__mainDebug.warn("出现全局错误申请，退出该进程!")
        self.__CloseStatus.set()
        self.__mainDebug.debug("进程中止")

    def play(self):
        self.__PauseStatus.clear()

    def pause(self):
        self.__PauseStatus.set()

    def stop(self):
        self.__CloseStatus.set()

    def isRunning(self):
        return not self.__PauseStatus.is_set()


class Gps(Process):

    def __init__(self, daemon=True, logging=True):
        super(Gps, self).__init__(daemon=daemon)
        if logging:
            self.__mainDebugQueue = Queue(5)
        else:
            self.__mainDebugQueue = None
        self.__mainDebug = Debug(self.__mainDebugQueue, time_switch=True, debug_name="下位机系统-数据模块-GPS数据进程")
        self.__CloseStatus = Event()
        self.__PauseStatus = Event()
        self.__serialNormal = serial.Serial("/dev/ttyUSB3", 115200, timeout=0)
        self.__serialGPS = serial.Serial("/dev/ttyUSB1", 115200, timeout=0)
        self.GPS_DATA = mps.Value(GpsData)

    async def __runAt(self, command: str) -> Optional[bytes]:
        self.__serialNormal.write((command + '\r\n').encode('ascii', 'ignore'))
        datas = b''
        lastTime = time.time()
        while True:
            data = self.__serialNormal.readline()
            if data == b'':
                if time.time() - lastTime >= 0.5:
                    break
                await asyncio.sleep(0)
            elif data == b'OK\r\n':
                datas += data
                return datas
            elif data == b'ERROR\r\n':
                datas += data
                return datas
            else:
                datas += data
                await asyncio.sleep(0)
        return None

    async def __getNetStatus(self) -> tuple:
        self.__mainDebug.info("[网络信号获取器] 已进入该任务")
        while not self.__PauseStatus.is_set():
            try:
                res = await self.__runAt('AT+CSQ')
                # print(f"res->{res}")
                if res is None:
                    return False, 0
                res = res.decode()
                pdB, status = re.search(r'\d+,\d+', res).group().split(',')
                signalOk = True
                if pdB == '99':
                    signalOk = False
                    signalStatus = 0
                else:
                    if (-113 + int(pdB) * 2) > -85:
                        signalStatus = 5
                    elif -85 >= (-113 + int(pdB) * 2) > -90:
                        signalStatus = 4
                    elif -90 >= (-113 + int(pdB) * 2) > -95:
                        signalStatus = 3
                    elif -95 >= (-113 + int(pdB) * 2) > -100:
                        signalStatus = 2
                    elif -100 >= (-113 + int(pdB) * 2) > -105:
                        signalStatus = 1
                    else:
                        signalOk = False
                        signalStatus = 0
                self.GPS_DATA.net.status = signalOk
                self.GPS_DATA.net.signal = signalStatus
                await asyncio.sleep(0.05)
            except Exception as e:
                self.__mainDebug.error(
                    f"\n{traceback.format_exc()}\n----------Error----------"
                )
                self.__mainDebug.error("[网络信号获取器] 该任务发生错误, 退出!")
                break
        if self.__PauseStatus.is_set():
            self.__mainDebug.warn("[网络信号获取器] 出现全局错误, 退出该任务!")
        self.__PauseStatus.set()
        self.__mainDebug.warn("[网络信号获取器] 任务被中止!")

    async def __getGpsInfo(self):
        self.__mainDebug.info("[GPS数据获取器] 已进入该任务")
        while not self.__PauseStatus.is_set():
            try:
                result = self.__serialGPS.readline()
                if result == b'':
                    await asyncio.sleep(0)
                else:
                    result = result.decode().strip()
                    if re.search(r'\$GPRMC', result) is not None:
                        # print(result)
                        resList = result.split(',')
                        timeStr = resList[1].split('.')[0]
                        if timeStr != '':
                            timeH, timeM, timeS = re.findall(r'\w{2}', timeStr)
                            timeH = int(timeH) + 8 - 24 if int(timeH) + 8 >= 24 else int(timeH) + 8
                            timeRes = f"{timeH}:{timeM}:{timeS}"
                        else:
                            timeRes = None
                        self.GPS_DATA.gps.time = timeRes
                        # ---------- 定位状态 ---------- #
                        locStat = resList[2]
                        if locStat == 'A':
                            locRes = True
                        elif locStat == 'V':
                            locRes = False
                        else:
                            locRes = None
                        self.GPS_DATA.gps.locStat = locRes
                        # ---------- 维度 ---------- #
                        dimFloat = resList[3]
                        # print(dimFloat)
                        if dimFloat != '':
                            dimDu = dimFloat[0:2]
                            dimFen = dimFloat[2:]
                            dimRes = int(dimDu) + float(dimFen) / 60
                            # print(dimRes)
                        else:
                            dimRes = None
                        self.GPS_DATA.gps.dim = -1 if dimRes is None else dimRes
                        # ---------- 南北纬 ---------- #
                        nsDimStat = resList[4]
                        if nsDimStat != '':
                            nsDimRes = nsDimStat
                        else:
                            nsDimRes = None
                        self.GPS_DATA.gps.nsDim = -1 if nsDimRes is None else nsDimRes
                        # ---------- 经度 ---------- #
                        litFloat = resList[5]
                        if litFloat != '':
                            litDu = litFloat[0:3]
                            litFen = litFloat[3:]
                            litRes = int(litDu) + float(litFen) / 60
                        else:
                            litRes = None
                        self.GPS_DATA.gps.lit = -1 if litRes is None else litRes
                        # ---------- 东西经 ---------- #
                        ewLitStat = resList[6]
                        if ewLitStat == '':
                            ewLitRes = None
                        else:
                            ewLitRes = ewLitStat
                        self.GPS_DATA.gps.ewLit = -1 if ewLitRes is None else ewLitRes
                        # ---------- 地面速率 km/h ---------- #
                        gdRate = resList[7]
                        if gdRate != '':
                            gdRateRes = round(float(gdRate) * 1.85, 2)
                        else:
                            gdRateRes = None
                        self.GPS_DATA.gps.gdRate = 0 if gdRateRes is None else gdRateRes
                        # ---------- 地面航向度 正北顺时针 ---------- #
                        gdDirect = resList[8]
                        if gdDirect != '':
                            gdDirRes = gdDirect
                        else:
                            gdDirRes = None
                        self.GPS_DATA.gps.gdDirect = gdDirRes
                        # ---------- 日期 ---------- #
                        dateStr = resList[9]
                        if dateStr != '':
                            dateD, dateM, dateY = re.findall(r'\w{2}', dateStr)
                            dateY = f"20{dateY}"
                            dateRes = f'{dateY}/{dateM}/{dateD}'
                        else:
                            dateRes = None
                        self.GPS_DATA.gps.date = dateRes
                        # await asyncio.sleep(0)
                    else:
                        await asyncio.sleep(0)
            except Exception as e:
                self.__mainDebug.error(
                    f"\n{traceback.format_exc()}\n----------Error----------"
                )
                self.__mainDebug.error("[GPS数据获取器] 该任务发生错误, 退出!")
                break
        if self.__PauseStatus.is_set():
            self.__mainDebug.warn("[GPS数据获取器] 出现全局错误, 退出该任务!")
        self.__PauseStatus.set()
        self.__mainDebug.warn("[GPS数据获取器] 任务被中止!")

    def run(self) -> None:
        self.__mainDebug.info("进程启动中...")
        while not self.__CloseStatus.is_set():
            try:
                self.__mainDebug.info("等待开启GPS数据采集服务器")
                while self.__PauseStatus.is_set():
                    if self.__CloseStatus.is_set():
                        break
                    time.sleep(0.1)
                if self.__CloseStatus.is_set():
                    break
                else:
                    self.__mainDebug.info("正在循环更新GPS数据...")

                    async def main():
                        res = await self.__runAt("AT+QGPS=1")
                        print(res)
                        taskList = [
                            self.__getGpsInfo(), self.__getNetStatus()
                        ]
                        await asyncio.gather(*taskList)

                    asyncio.run(main())
                    self.__mainDebug.info("全任务结束, 重新进入等待")
            except Exception as e:
                self.__mainDebug.error(
                    f"\n{traceback.format_exc()}\n----------Error----------"
                )
                self.__mainDebug.error("发生异常情况, 退出该进程")
                break
        if self.__CloseStatus.is_set():
            self.__mainDebug.warn("出现全局错误申请，退出该进程!")
        self.__CloseStatus.set()
        self.__mainDebug.debug("进程中止")

    def play(self):
        self.__PauseStatus.clear()

    def pause(self):
        self.__PauseStatus.set()

    def stop(self):
        self.__CloseStatus.set()

    def isRunning(self):
        return not self.__PauseStatus.is_set()


class Control(Process):

    def __init__(self, daemon=True, logging=True):
        super(Control, self).__init__(daemon=daemon)
        if logging:
            self.__mainDebugQueue = Queue(5)
        else:
            self.__mainDebugQueue = None
        self.__mainDebug = Debug(self.__mainDebugQueue, time_switch=True, debug_name="下位机系统-数据模块-串口数据交换进程")
        self.__CloseStatus = Event()
        self.__PauseStatus = Event()
        self.__serial = serial.Serial("/dev/ttyAMA0", 115200, timeout=0)
        self.SERIAL_DATA = mps.Value(SerialData)

    async def __getSerialData(self):
        self.__mainDebug.info("[Serial数据获取器] 已进入该任务")
        serialRecvPack = struct.Struct("> B B f h f c")
        while not self.__PauseStatus.is_set():
            try:
                if self.__serial.in_waiting == 13:
                    rawData = self.__serial.read(self.__serial.in_waiting)
                    data = serialRecvPack.unpack(rawData)
                    # print(data)
                    self.SERIAL_DATA.speed_stm32 = data[2]
                    self.SERIAL_DATA.bat = data[3]
                    self.SERIAL_DATA.yaw = data[4]
                    # print(f"STM32->{self.SERIAL_DATA.speed_stm32}")
                else:
                    # print(self.__serial.in_waiting)
                    self.__serial.flushInput()
                    await asyncio.sleep(0)
            except Exception as e:
                self.__mainDebug.error(
                    f"\n{traceback.format_exc()}\n----------Error----------"
                )
                self.__mainDebug.error("[Serial数据获取器] 该任务发生错误, 退出!")
                break
        if self.__PauseStatus.is_set():
            self.__mainDebug.warn("[Serial数据获取器] 出现全局错误, 退出该任务!")
        self.__PauseStatus.set()
        self.__mainDebug.warn("[Serial数据获取器] 任务被中止!")

    async def __sentSerialData(self):
        self.__mainDebug.info("[Serial数据发送器] 已进入该任务")
        serialSentPack = struct.Struct("> B B B f")
        while not self.__PauseStatus.is_set():
            try:
                await asyncio.sleep(0.1)
                status = self.SERIAL_DATA.status
                speed = self.SERIAL_DATA.speed_qt
                # print(status)
                # print(speed)
                dataRaw = serialSentPack.pack(255, 254, status, speed)
                # print(binascii.hexlify(dataRaw))
                self.__serial.write(dataRaw)
            except Exception as e:
                self.__mainDebug.error(
                    f"\n{traceback.format_exc()}\n----------Error----------"
                )
                self.__mainDebug.error("[Serial数据发送器] 该任务发生错误, 退出!")
                break
        if self.__PauseStatus.is_set():
            self.__mainDebug.warn("[Serial数据发送器] 出现全局错误, 退出该任务!")
        self.__PauseStatus.set()
        self.__mainDebug.warn("[Serial数据发送器] 任务被中止!")

    def run(self) -> None:
        self.__mainDebug.info("进程启动中")
        while not self.__CloseStatus.is_set():
            try:
                self.__mainDebug.info("等待开启串口数据交换服务器")
                while self.__PauseStatus.is_set():
                    if self.__CloseStatus.is_set():
                        break
                    time.sleep(0.1)
                if self.__CloseStatus.is_set():
                    break
                else:
                    self.__mainDebug.info("正在循环更新串口数据...")

                    async def main():
                        taskList = [
                            self.__getSerialData(), self.__sentSerialData()
                        ]
                        await asyncio.gather(*taskList)

                    asyncio.run(main())
                    self.__mainDebug.info("全任务结束, 重新进入等待")
            except Exception as e:
                self.__mainDebug.error(
                    f"\n{traceback.format_exc()}\n----------Error----------"
                )
                self.__mainDebug.error("发生异常情况, 退出该进程")
                break
        if self.__CloseStatus.is_set():
            self.__mainDebug.warn("出现全局错误申请, 退出该进程!")
        self.__CloseStatus.set()
        self.__mainDebug.debug("进程中止")

    def play(self):
        self.__PauseStatus.clear()

    def pause(self):
        self.__PauseStatus.set()

    def stop(self):
        self.__CloseStatus.set()

    def isRunning(self):
        return not self.__PauseStatus.is_set()


if __name__ == '__main__':
    # mainBoard = MainBoard()
    # mainBoard.start()
    # mainBoard.play()
    # mainBoard.join()

    # gpsData = Gps()
    # gpsData.start()
    # gpsData.play()
    # gpsData.join()

    controlData = Control()
    controlData.start()
    controlData.play()
    controlData.join()
